package com.yupu.springboot_web.config;



import com.yupu.springboot_web.filter.TokenFilter;
import com.yupu.springboot_web.handler.AppLogoutSuccessHandler;
import com.yupu.springboot_web.handler.MyAccessDeniedHandler;
import com.yupu.springboot_web.handler.MyAuthenticationFailureHandler;
import com.yupu.springboot_web.handler.MyAuthenticationSuccessHandler;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

/**
 * 功能：新版的Spring Security配置类中已经删除了WebSecurityConfigurerAdapter类，改为了直接使用WebSecurityConfigurer接口，
 * 同时使用泛型的方式来配置，这样可以更加灵活地使用Spring Security。
 * 改造： 无需实现，直接注册Bean即可。
 * 作者：zyp
 * 日期：2025/3/12 17:47
 */
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {


    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Autowired
    private AppLogoutSuccessHandler appLogoutSuccessHandler;
    @Autowired
    private MyAccessDeniedHandler myAccessDeniedHandler;
    @Autowired
    private TokenFilter tokenFilter;

    //创建密码加密器BCryptPasswordEncoder
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /***
     * 配置跨域。前后端分离开发时，可以通过这种方式访问到后端接口。
     * @return
     */
    @Bean //配置跨域
    public CorsConfigurationSource configurationSource() {
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();

        //跨域配置
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //允许任何来源的任何请求方法（post、get、put、delete），任何请求头（jwt）都可以访问
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("*"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));

        //注册跨域配置，其中第一个参数是访问路径，第二个参数是跨域配置
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); //  /api/user,  /api/user/12082
        return urlBasedCorsConfigurationSource;
    }

   //配置放行的路径，新版写法
//    @Bean
//    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//        //关闭csrf
//        http.csrf(csrf -> csrf.disable());
//        //配置请求方式的拦截
//        http.authorizeHttpRequests(auth -> auth.requestMatchers("/user/login")
//                .permitAll()
//                .anyRequest()
//                .authenticated());
//        //添加自定义的JwtAuthenticationTokenFilter过滤器，配置执行顺序，优先级最高
//        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
//        return http.build();
//    }
   //配置spring security框架的一些行为（配置我们自己的登录页，不使用框架默认的登录页）
   //但是当你配置了SecurityFilterChain这个Bean之后，Spring security框架的某些默认行为就弄丢了（失效了），此时你需要加回来（捡回来）
   @Bean //安全过滤器链Bean
   public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, CorsConfigurationSource configurationSource) throws Exception { //httpSecurity是方法参数注入Bean
       //在spring security框架开发时，创建SecurityFilterChain这个Bean，不是直接new DefaultSecurityFilterChain
       //return new DefaultSecurityFilterChain();

       return httpSecurity
               //配置我们自己的登录页,其中httpSecurity的formLogin方法,用于表单登录的配置方法，
               //其参数是Customizer<FormLoginConfigurer<HttpSecurity>> formLoginCustomizer，但是由于我们自己不好new这个函数接口，所以就直接用这个lambda表达式
               //由于参数列表是(T t)，所以泛型T让他直接类型推断。
               .formLogin( (formLogin) -> {
                   // 框架默认接收登录提交请求的地址是 /login，但是我们把它给弄丢了，需要捡回来
                   formLogin.loginProcessingUrl("/user/login") //登录的账号密码往哪个地址提交
                           .successHandler(myAuthenticationSuccessHandler) //登录成功后执行该handler
                           .failureHandler(myAuthenticationFailureHandler);// 登录失败后执行该handler
                   // 在访问 /user/login 地址之前，对于后端应用来说，没有上一个地址（原地），那默认是跳转到项目的根路径斜杆/
               })

               .logout((logout) -> {
                   logout.logoutUrl("/user/logout") //退出请求提交到哪个地址
                           .logoutSuccessHandler(appLogoutSuccessHandler); //退出成功后执行该handler
                   //退出失败呢？Spring Security框架没有退出失败的处理
               })

               //把所有接口都会进行登录状态检查的默认行为，再加回来
               .authorizeHttpRequests( (authorizeHttpRequests) -> {
                   authorizeHttpRequests
                           .anyRequest().authenticated(); //除了上面的特殊情况外，其他任何对后端接口的请求，都需要认证（登录）后才能访问
               })

               .csrf( (csrf) -> {
                   //禁用csrf跨站请求伪造，禁用之后，肯定就不安全了，有csrf网络攻击的风险，后续加入jwt是可以防御的
                   csrf.disable();
               })

               .cors( (cors) -> { //允许前端跨域访问
                   cors.configurationSource(configurationSource);
               })

               .sessionManagement((sessionManagement) -> {
                   //session创建策略 (无session状态)
                   sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
               })

               //在登录Filter后面加入我们的token验证的Filter
               .addFilterBefore(tokenFilter, LogoutFilter.class)

               .exceptionHandling((exceptionHandling) -> {
                   exceptionHandling.accessDeniedHandler(myAccessDeniedHandler); //没有权限的时候，执行该handler
               })

               .build();
   }

}
